// -----------------------------------------------------------------------------
//
//  Author :    Duke Zhou
//  Data :      2015/12/22
//
// -----------------------------------------------------------------------------
//
using UnityEngine;
using System.Collections.Generic;
using FibMatrix;
using FibMatrix.DS;
using FibMatrix.Schedule;
using FibMatrix.Utils;
using FM_Mono;

namespace SleepyHippo.Util
{
    public partial class GameObjectPool2
	{
		// private float CHECK_INTERNAL = 10f;
		//
		private float DEATH_TIME = 5 * 60f;
		
		class GPoolNode:System.IDisposable
		{
			public GameObject go;
			public string name;
			public bool used;

			public void Dispose()
			{
				go = null;
				name = null;
				used = false;
			}

			public static GPoolNode Allocate()
			{
				return new GPoolNode();
			}
		}

		class GQueue : Queue<GPoolNode>
		{
			public GQueue(int capacity) : base(capacity)
			{
			}

			public double lastIdleTime;

			public string name;

			public int totalAllocNum;
			
			public Vector3 scale;

			public int layer;
		}

		sealed class GQueuePool
		{
            sealed class GQueueInternal : GQueue
            {
                public GQueueInternal(int capacity) : base(capacity)
                {
                }

				public int idx;
            }

            private List<GQueueInternal> _queueList = new List<GQueueInternal>(10);

			private int _liveCount = 0;

			public int liveCount
			{
				get
				{
					return _liveCount;
				}
			}

			public GQueue GetLiveQueueAt(int index)
			{
				if (index >= 0 && index < _liveCount)
				{
					return _queueList[index];
				}
				return null;
			}

			public GQueue Allocate(int capacity, string name)
			{
				GQueueInternal queue = null;
				if (_liveCount < _queueList.Count)
				{
					queue = _queueList[_liveCount];
				}
				else
				{
					queue = new GQueueInternal(capacity);
					_queueList.Add(queue);
				}
				queue.idx = _liveCount;
				queue.name = name;
				queue.totalAllocNum = 0;
				queue.lastIdleTime = -1f;
				++_liveCount;
				return queue;
			}

			public void Recycle(GQueue queue)
			{
				GQueueInternal q = queue as GQueueInternal;
				q.Clear();
				if (_liveCount > 1)
				{
					--_liveCount;
					int oIdx = q.idx;
					GQueueInternal lastQ = _queueList[_liveCount];
					_queueList[_liveCount] = q;
					_queueList[oIdx] = lastQ;

					q.idx = _liveCount;
					lastQ.idx = oIdx;
				}
				else
				{
					_liveCount = 0;
				}
			}
		}
        //      public const string PoolObjectPrefix = "[P]";
        private static GameObjectPool2 _instance;
		// private static GameObjectPool2 _instanceNoClear;

		private ObjectPool2<GPoolNode> _nodePool = new ObjectPool2<GPoolNode>(100, GPoolNode.Allocate);

		private GQueuePool _queuePool = new GQueuePool();
		
		private Dictionary<string, GQueue> _availableNodesMap;
		// private Dictionary<string, LinkedList<GameObject>> _usingListMap;
		/// <summary>
		/// Dictionary<go.name, Dictionary<go.GetInstanceID(), LinkedListNode<GameObject>>;
		/// </summary>
		private Dictionary<int, GPoolNode> _allNodeMap;
		// private Dictionary<string, Dictionary<int, LinkedListNode<GameObject>>> _linkedListNodeMaps;
		private double _lastCheckTime;
		
		private int _checkReleaseHandler;
		private int _instantiateHandler;
		
		private GameObject _emptyGOTemplate;
		public GameObject emptyGOTemplate
		{
			get
			{
				if(_emptyGOTemplate == null)
				{
					_emptyGOTemplate = new GameObject("EmptyParent");
					_emptyGOTemplate.SetActive(false);
					_emptyGOTemplate.transform.parent = poolParent;
				}
				return _emptyGOTemplate;
			}
		}

		public void SetUIPoolContainer(Transform uiPoolContainer)
		{
			_uiPoolContainer = uiPoolContainer;
			if (_uiPoolParent)
			{
				_uiPoolParent.SetParent(uiPoolContainer, true);
			}
		}
		
		private Transform _uiPoolContainer;
		private Transform _uiPoolParent;
		public Transform uiPoolParent
		{
			get
			{
				if(_uiPoolParent == null)
				{
					_uiPoolParent = new GameObject("[UIPool]").transform;
                    _uiPoolParent.transform.parent = _uiPoolContainer;
                    _uiPoolParent.gameObject.layer = _uiPoolContainer ? _uiPoolContainer.gameObject.layer : LayerMask.NameToLayer("UI");
					CommonUtil.ResetTransform(_uiPoolParent);
				}
				return _uiPoolParent;
			}
		}
		private Transform _poolParent;
		public Transform poolParent
		{
			get
			{
				if(_poolParent == null)
				{
					_poolParent = new GameObject("[Pool]").transform;
					GameObject.DontDestroyOnLoad(_poolParent);
				}
				return _poolParent;
			}
		}
		
		[AcquireOnce]
		public static GameObjectPool2 instance {
			get 
			{
				if (_instance == null)
				{
					_instance = new GameObjectPool2 ();
				}
				
				return _instance;
			}
		}
		
		public int spawnCount
		{
			get;
			private set;
		}
		
		public GameObjectPool2 ()
		{
			_availableNodesMap = new Dictionary<string, GQueue>(20);
			// _usingListMap = new Dictionary<string, LinkedList<GameObject>>();
			// _linkedListNodeMaps = new Dictionary<string, Dictionary<int, LinkedListNode<GameObject>>>();
			_allNodeMap = new Dictionary<int, GPoolNode>(100);
			_lastCheckTime = FibMatrix.RealTimer.elapsedSeconds;
		}

		public void Initialize(float checkInternal, float deathTime, int instantiateLimit)
		{
			DEATH_TIME = deathTime;
			_instantiateFrameLimit = instantiateLimit / 1000f;
			
			_checkReleaseHandler = GlobalScheduler.AddTimeLimitUpdator(checkInternal, Tick);
			_instantiateHandler = GlobalScheduler.AddFrameLimitUpdator(1, InstantiateQueueTick);
		}

		public void Dispose()
		{
			Clear();
			
			GlobalScheduler.RemoveLimitUpdator(ref _checkReleaseHandler);

			GlobalScheduler.RemoveLimitUpdator(ref _instantiateHandler);
		}
		
		private void Allocate (object prefabTemplateOrPath, string cacheKey, int count = 10)
		{
			if (prefabTemplateOrPath == null)
			{
				Debug.LogError($"GameObjPool Allocate failed:{cacheKey}");
				return;
			}
			string prefabPath = prefabTemplateOrPath as string;
			GQueue availableQueue = SafelyGetAvailableQueue(cacheKey);
			for (int i = 0; i < count; ++i)
			{
				GameObject newGameObject = prefabPath != null ? 
#if !DISABLE_SYNC_LOAD
					ResourceFacade.instance.LoadPrefab(prefabPath) : 
#else
					null :
#endif
					ResourceFacade.Instantiate(prefabTemplateOrPath as GameObject);
				if (newGameObject == null)
				{
					Debug.LogError($"GameObjPool Allocate failed2:{cacheKey}");
					return;
				}
				if (availableQueue.totalAllocNum == 0)
				{
					availableQueue.scale = newGameObject.transform.localScale;
					availableQueue.layer = newGameObject.layer;
				}
				++availableQueue.totalAllocNum;
				newGameObject.gameObject.SetActive(false);
				newGameObject.layer = availableQueue.layer;
				//newGameObject.name = key;
			    newGameObject.transform.SetParent(GetPoolParent(newGameObject), false);

				GPoolNode node = _nodePool.Allocate();
				node.go = newGameObject;
				node.name = cacheKey;
				node.used = false;
                availableQueue.Enqueue(node);
				int newGameObjectId = newGameObject.GetInstanceID();
				_allNodeMap.Add(newGameObjectId, node);
			}
		}
		
#if !DISABLE_SYNC_LOAD
		/// <summary>
		/// 
		/// </summary>
		/// <param name="template">要创建的模板</param>
		/// <param name="preAllocateCount">预先初始化的对象数量</param>
		/// <param name="autoActive">创建出GameObject之后是否自动显示，有时候不希望自动显示，例如需要把一个Widget从一个Panel移动到另一个Panel时，如果先Active了，就需要Inactive后再次Active才能正确显示</param>
		/// <param name="cacheName">自定义缓存对象的名字，比如外面要改gameobject的名字</param>
		public GameObject Spawn (string prefabPath, int preAllocateCount = 10, bool autoActive = true, Transform newParent = null)
		{
			return DoSpawn(prefabPath, prefabPath, preAllocateCount, autoActive, newParent);
		}
		
		public GameObject SpawnEmptyGO (string cacheKey, int preAllocateCount = 10, Transform newParent = null)
		{
			return DoSpawn(emptyGOTemplate, cacheKey, preAllocateCount, true, newParent);        
		}

		private GameObject DoSpawn(object prefabTemplateOrPath, string cacheKey, int preAllocateCount = 10, bool autoActive = true, Transform newParent = null)
		{
			GQueue availableQueue = SafelyGetAvailableQueue(cacheKey);
			if(availableQueue.Count == 0)
			{
				Allocate(prefabTemplateOrPath, cacheKey, preAllocateCount);
			}

			GameObject gameObject = null;
			if (availableQueue.Count > 0)
			{
				GPoolNode firstAvailableNode = availableQueue.Dequeue();
				availableQueue.lastIdleTime = -1f;
				gameObject = firstAvailableNode.go;
				if(gameObject != null)
				{
					if (newParent != null)
					{
						gameObject.transform.SetParent (newParent, false);
					}
					if(autoActive && !gameObject.activeSelf)
					{
						gameObject.SetActive(true);
					}
					spawnCount++;
					firstAvailableNode.used = true;
				}
				else
				{
	#if UNITY_EDITOR
					Debug.LogError("[EditorLog]GameObjectPool's GameObject is null");//已修复，但如果外界使用不当会出现这个问题
	#endif
					return null;
				}
			}
			return gameObject;
		}
#endif
		
		[IgnoreGen]
		public void Tick(object context)
		{
			// if (FibMatrix.RealTimer.elapsedSeconds - _lastCheckTime > CHECK_INTERNAL)
			{
				_lastCheckTime = FibMatrix.RealTimer.elapsedSeconds;
				for (int i = 0; i < _queuePool.liveCount; )
				{
					GQueue queue = _queuePool.GetLiveQueueAt(i);
					if (queue.lastIdleTime > 0 && FibMatrix.RealTimer.elapsedSeconds - queue.lastIdleTime > DEATH_TIME)
					{
						DoClearQueue(queue);
					}
					else
					{
						++i;
					}
				}
			}
		}
		
		public void Recycle (GameObject go, bool moveToPoolParent = true)
		{
			int goId = go.GetInstanceID();

			GPoolNode node;
			if(!_allNodeMap.TryGetValue(goId, out node))
			{
				Debug.LogError("Try to recycle a wrong GameObject " + go.name);
				GameObject.Destroy(go);
				return;
			}

			if (node.used == false)
			{
				Debug.LogError("Try to recycle a queued GameObject " + go.name);
				return;
			}
			
			// else
			// {
			// 	LinkedList<GameObject> usingList = SafelyGetUsingList(key);
			// 	try
			// 	{
			// 		usingList.Remove(node);
			// 	}
			// 	catch(System.InvalidOperationException)
			// 	{
			// 		Debug.LogError("Node " + node + " is not in usingList");
			// 		GameObject.Destroy(go);
			// 		return;
			// 	}
			// }
			spawnCount--;
			string key = node.name;
			GQueue availableList = SafelyGetAvailableQueue(key);
			
			if(moveToPoolParent)
			{
				go.transform.SetParent(GetPoolParent(go), false);
			}
			go.SetActive(false);
			node.used = false;
			availableList.Enqueue(node);

			go.transform.localScale = availableList.scale;

			if (availableList.Count == availableList.totalAllocNum)
			{
				availableList.lastIdleTime = FibMatrix.RealTimer.elapsedSeconds;
			}
		}
		
		public void Clear()
		{
			var iter = _allNodeMap.GetEnumerator();
			while(iter.MoveNext())
			{
				GPoolNode node = iter.Current.Value;
				if (node.go == null)
				{
					Debug.LogError(node.name + " recycle error");
				}
				else
				{
					ResourceFacade.instance.Unload(node.go);
				}
				_nodePool.Recycle(node);
			}
			_allNodeMap.Clear();
			
			var iter2 = _availableNodesMap.GetEnumerator();
			while(iter2.MoveNext())
			{
				_queuePool.Recycle(iter2.Current.Value);
			}
			_availableNodesMap.Clear();
		}

		private static List<int> _cachedKeyList = new List<int>(100);
		public void Clear(string cacheName)
		{
			GQueue queue;
			if (_availableNodesMap.TryGetValue(cacheName, out queue))
			{
				DoClearQueue(queue);
			}
		}

		private void DoClearQueue(GQueue queue)
		{
			var iter = _allNodeMap.GetEnumerator();
			_cachedKeyList.Clear();
			string cacheName = queue.name;
			while(iter.MoveNext())
			{
				GPoolNode node = iter.Current.Value;
				if (node.name == cacheName)
				{
					if (node.go == null)
					{
						Debug.LogError(iter.Current.Key + " recycle error");
					}
					else
					{
						ResourceFacade.instance.Unload(node.go);
					}
					_nodePool.Recycle(node);
					_cachedKeyList.Add(iter.Current.Key);
				}
			}
			int count = _cachedKeyList.Count;
			for (int i = 0; i < count; ++i)
			{
				_allNodeMap.Remove(_cachedKeyList[i]);
			}

			_availableNodesMap.Remove(cacheName);

			_queuePool.Recycle(queue);
		}
		
		private Transform GetPoolParent(GameObject go)
		{
            if(go.layer == uiPoolParent.gameObject.layer)
				return uiPoolParent;
			else
				return poolParent;
		}
		
		/// <summary>
		/// 理论上不可能返回null？
		/// </summary>
		private GQueue SafelyGetAvailableQueue(string key)
		{
			GQueue availableQueue;
			if(!_availableNodesMap.TryGetValue(key, out availableQueue))
			{
				availableQueue = _queuePool.Allocate(10, key);
				_availableNodesMap.Add(key, availableQueue);
			}
			return availableQueue;
		}
		
		// LinkedList<GameObject> SafelyGetUsingList(string key)
		// {
		// 	LinkedList<GameObject> usingList;
		// 	if(!_usingListMap.TryGetValue(key, out usingList))
		// 	{
		// 		usingList = new LinkedList<GameObject>();
		// 		_usingListMap.Add(key, usingList);
		// 	}
		// 	return usingList;
		// }
		
		// Dictionary<int, LinkedListNode<GameObject>> SafelyGetNodeMap(string key)
		// {
		// 	Dictionary<int, LinkedListNode<GameObject>> nodeMap;
		// 	if(!_linkedListNodeMaps.TryGetValue(key, out nodeMap))
		// 	{
		// 		nodeMap = new Dictionary<int, LinkedListNode<GameObject>>();
		// 		_linkedListNodeMaps.Add(key, nodeMap);
		// 	}
		// 	return nodeMap;
		// }
		
		public void LogUsingObject()
		{
			#if UNITY_EDITOR
			// var iter = _usingListMap.GetEnumerator();
			// while(iter.MoveNext())
			// {
			// 	LinkedList<GameObject> usingList = iter.Current.Value;
			// 	var innerIter = usingList.GetEnumerator();
			// 	while(innerIter.MoveNext())
			// 	{
			// 		GameObject go = innerIter.Current;
			// 		if(go != null)
			// 		{
			// 			Debug.LogWarning(string.Format("[EditorLog]GameObjectPool2's {0} is in using", go.name));
			// 		}
			// 		else
			// 		{
			// 			Debug.LogError("[EditorLog]GameObjectPool2's gameobject is null");
			// 		}
			// 	}
			// }
			var iter = _allNodeMap.GetEnumerator();
			while(iter.MoveNext())
			{
				GPoolNode node = iter.Current.Value;
				if (node.go != null)
				{
					Debug.LogWarning(string.Format("[EditorLog]GameObjectPool2's {0} is in using", node.go.name));
				}
				else
				{
					Debug.LogError("[EditorLog]GameObjectPool2's gameobject is null");
				}
			}
			#endif
		}
		
	}
}
